/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2008-2015 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include "vvp_island.h"

#include "compile.h"
#include "config.h"
#include "schedule.h"
#include "symbols.h"
#ifdef CHECK_WITH_VALGRIND
#include "vvp_cleanup.h"
#endif
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <list>

#include "ivl_alloc.h"

static bool at_EOS = false;

void island_send_value(vvp_net_t* net, const vvp_vector8_t& val) {
  vvp_island_port* fun = dynamic_cast<vvp_island_port*>(net->fun);
  if (fun->outvalue.eeq(val)) return;

  fun->outvalue = val;
  net->send_vec8(fun->outvalue);
}

/*
 * Implementations...
 */
vvp_island::vvp_island() {
  flagged_ = false;
  branches_ = 0;
  ports_ = 0;
  anodes_ = 0;
  bnodes_ = 0;
}

vvp_island::~vvp_island() {
  // We can only delete islands at the end of simulation.
  if (!at_EOS) assert(0);

  while (branches_) {
    vvp_island_branch* next = branches_->next_branch;
    delete branches_;
    branches_ = next;
  }
}

void vvp_island::flag_island() {
  if (flagged_ == true) return;

  schedule_generic(this, 0, false, false);
  flagged_ = true;
}

/*
 * This method handles the callback from the scheduler. It does basic
 * housecleaning and calls the run_island() method implemented by the
 * derived class.
 */
void vvp_island::run_run() {
  flagged_ = false;
  run_island();
}

void vvp_island::add_port(const char* key, vvp_net_t* net) {
  if (ports_ == 0) ports_ = new symbol_map_s<vvp_net_t>;

  // each port should have a unique label
  assert(ports_->sym_get_value(key) == 0);

  ports_->sym_set_value(key, net);
}

void vvp_island::add_branch(vvp_island_branch* branch, const char* pa,
                            const char* pb) {
  vvp_island_branch* cur;
  assert(ports_);
  branch->a = ports_->sym_get_value(pa);
  branch->b = ports_->sym_get_value(pb);
  assert(branch->a && branch->b);

  vvp_branch_ptr_t ptra(branch, 0);
  vvp_branch_ptr_t ptrb(branch, 1);
  if (anodes_ == 0) anodes_ = new symbol_map_s<vvp_island_branch>;
  if (bnodes_ == 0) bnodes_ = new symbol_map_s<vvp_island_branch>;

  if ((cur = anodes_->sym_get_value(pa))) {
    branch->link[0] = cur->link[0];
    cur->link[0] = ptra;
  } else if ((cur = bnodes_->sym_get_value(pa))) {
    branch->link[0] = cur->link[1];
    cur->link[1] = ptra;
  } else {
    branch->link[0] = ptra;
    anodes_->sym_set_value(pa, branch);
  }

  if ((cur = anodes_->sym_get_value(pb))) {
    branch->link[1] = cur->link[0];
    cur->link[0] = ptrb;
  } else if ((cur = bnodes_->sym_get_value(pb))) {
    branch->link[1] = cur->link[1];
    cur->link[1] = ptrb;
  } else {
    branch->link[1] = ptrb;
    bnodes_->sym_set_value(pb, branch);
  }

  branch->next_branch = branches_;
  branches_ = branch;
}

vvp_net_t* vvp_island::find_port(const char* key) {
  if (ports_ == 0)
    return 0;
  else
    return ports_->sym_get_value(key);
}

void vvp_island::compile_cleanup() {
  delete ports_;
  ports_ = 0;

  delete anodes_;
  anodes_ = 0;

  delete bnodes_;
  bnodes_ = 0;
}

vvp_island_port::vvp_island_port(vvp_island* ip) : island_(ip) {}

vvp_island_port::~vvp_island_port() {}

void vvp_island_port::recv_vec4(vvp_net_ptr_t, const vvp_vector4_t& bit,
                                vvp_context_t) {
  vvp_vector8_t tmp(bit, 6, 6);
  if (invalue.eeq(tmp)) return;

  invalue = tmp;
  island_->flag_island();
}

void vvp_island_port::recv_vec4_pv(vvp_net_ptr_t port, const vvp_vector4_t& bit,
                                   unsigned base, unsigned wid, unsigned vwid,
                                   vvp_context_t) {
  vvp_vector8_t tmp(bit, 6, 6);
  recv_vec8_pv(port, tmp, base, wid, vwid);
}

void vvp_island_port::recv_vec8(vvp_net_ptr_t, const vvp_vector8_t& bit) {
  if (invalue.eeq(bit)) return;

  invalue = bit;
  island_->flag_island();
}

void vvp_island_port::recv_vec8_pv(vvp_net_ptr_t, const vvp_vector8_t& bit,
                                   unsigned base, unsigned wid, unsigned vwid) {
  if (invalue.size() == 0) {
    assert(bit.size() == wid);
    invalue = part_expand(bit, vwid, base);
  } else {
    assert(invalue.size() == vwid);
    for (unsigned idx = 0; idx < wid; idx += 1) {
      if ((base + idx) >= vwid) break;
      invalue.set_bit(base + idx, bit.value(idx));
    }
  }

  island_->flag_island();
}

void vvp_island_port::force_flag(bool run_now) {
  if (run_now)
    island_->run_island();
  else
    island_->flag_island();
}

vvp_island_branch::~vvp_island_branch() {}

void island_collect_node(list<vvp_branch_ptr_t>& conn, vvp_branch_ptr_t cur) {
  conn.push_back(cur);
  for (vvp_branch_ptr_t idx = next(cur); idx != cur; idx = next(idx))
    conn.push_back(idx);
}

/* **** COMPILE/LINK SUPPORT **** */

/*
 * We need to keep an island symbol table to make island labels to
 * islands, and we need a list of the islands that we can run through
 * during cleanup. After linking is done, the compile_island_cleanup() is
 * called to erase the symbol table, we still need the list to cleanup the
 * island memory at EOS.
 */
static symbol_map_s<vvp_island>* island_table = 0;
static vvp_island** island_list = 0;
static unsigned island_count = 0;

#ifdef CHECK_WITH_VALGRIND
void island_delete() {
  at_EOS = true;
  for (unsigned idx = 0; idx < island_count; idx += 1) {
    delete island_list[idx];
  }
  free(island_list);
  island_list = 0;
  island_count = 0;
}
#endif

void compile_island_base(char* label, vvp_island* use_island) {
  if (island_table == 0) island_table = new symbol_map_s<vvp_island>;

  island_table->sym_set_value(label, use_island);
  island_count += 1;
  island_list =
      (vvp_island**)realloc(island_list, island_count * sizeof(vvp_island**));
  island_list[island_count - 1] = use_island;
  free(label);
}

vvp_island* compile_find_island(const char* island) {
  assert(island_table);
  vvp_island* use_island = island_table->sym_get_value(island);
  assert(use_island);
  return use_island;
}

/*
 * This handles the compile of a .port record. A .port is a 2-way port
 * between the island and the outside. For example,
 *
 *   <label> .port <island> <src> ;
 *
 * The <src> is a label in the domain outside the island, and the
 * <label> is in the domain inside the island. Since this port is
 * bi-directional, the <label> is also available in the domain outside
 * the island. The outside should use the <label> to access the nexus
 * that this port represents, because the island will resolve internal
 * drivers with the external driver and make the output available on
 * <label>.
 */
void compile_island_port(char* label, char* island, char* src) {
  assert(island_table);
  vvp_island* use_island = island_table->sym_get_value(island);
  assert(use_island);
  free(island);

  vvp_net_t* net = new vvp_net_t;
  vvp_island_port* fun = new vvp_island_port(use_island);
  net->fun = fun;

  // Get the source from outside the island
  input_connect(net, 0, src);

  // Define the functor outside the island.
  define_functor_symbol(label, net);

  // Also define it inside the island.
  use_island->add_port(label, net);

  free(label);
}

void compile_island_export(char* label, char* island) {
  fprintf(stderr, "XXXX %s .export %s;\n", label, island);
  free(label);
  free(island);
}

/*
 * This handles the compile of a .import record. A .import is an input
 * port into the island from the outside domain. For example,
 *
 *   <label> .import <island> <src> ;
 *
 * The <src> is a label in the domain outside the island, and the
 * <label> is in the domain inside the island. Branches within the
 * island use the <label>.
 */
void compile_island_import(char* label, char* island, char* src) {
  assert(island_table);
  vvp_island* use_island = island_table->sym_get_value(island);
  assert(use_island);
  free(island);

  vvp_net_t* net = new vvp_net_t;
  vvp_island_port* fun = new vvp_island_port(use_island);
  net->fun = fun;

  // Get the source from outside the island
  input_connect(net, 0, src);

  // Define the functor only inside the island.
  use_island->add_port(label, net);

  free(label);
}

void compile_island_cleanup(void) {
  // Call the per-island cleanup to get rid of local symbol tables.
  for (unsigned idx = 0; idx < island_count; idx += 1) {
    island_list[idx]->compile_cleanup();
  }

  // If we are not doing valgrind checking then free the list.
#ifndef CHECK_WITH_VALGRIND
  free(island_list);
  island_list = 0;
  island_count = 0;
#endif

  // Remove the island symbol table itself.
  delete island_table;
  island_table = 0;
}
